{
 "cells": [
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T08:07:51.605751Z",
     "start_time": "2025-01-26T08:07:48.183126Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.1\n",
      "torch 2.5.1+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "execution_count": 1
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 加载数据"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T08:07:52.965332Z",
     "start_time": "2025-01-26T08:07:51.606751Z"
    }
   },
   "source": [
    "from torchvision import datasets\n",
    "from torchvision.transforms import ToTensor\n",
    "\n",
    "# fashion_mnist图像分类数据集\n",
    "train_ds = datasets.FashionMNIST(\n",
    "    root=\"data\",\n",
    "    train=True,\n",
    "    download=True,\n",
    "    transform=ToTensor()\n",
    ")\n",
    "\n",
    "test_ds = datasets.FashionMNIST(\n",
    "    root=\"data\",\n",
    "    train=False,\n",
    "    download=True,\n",
    "    transform=ToTensor()\n",
    ")\n",
    "\n",
    "# 从数据集到dataloader\n",
    "train_loader = torch.utils.data.DataLoader(train_ds, batch_size=16, shuffle=True)\n",
    "val_loader = torch.utils.data.DataLoader(test_ds, batch_size=16, shuffle=False)"
   ],
   "outputs": [],
   "execution_count": 2
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T08:07:58.649597Z",
     "start_time": "2025-01-26T08:07:52.965835Z"
    }
   },
   "source": [
    "from torchvision.transforms import Normalize\n",
    "\n",
    "# 遍历train_ds得到每张图片，计算每个通道的均值和方差\n",
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds: # 遍历每张图片,img.shape=[1,28,28]\n",
    "        mean += img.mean(dim=(1, 2))\n",
    "        std += img.std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "\n",
    "print(cal_mean_std(train_ds))  # 0.2860， 0.3205\n",
    "\n",
    "transforms = nn.Sequential(\n",
    "    Normalize([0.2860], [0.3205]) \n",
    ")"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([0.2860]), tensor([0.3205]))\n"
     ]
    }
   ],
   "execution_count": 3
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型\n",
    "\n",
    "这里我们没有用`nn.Linear`的默认初始化，而是采用了xavier均匀分布去初始化全连接层的权重\n",
    "\n",
    "xavier初始化出自论文 《Understanding the difficulty of training deep feedforward neural networks》，适用于使用`tanh`和`sigmoid`激活函数的方法。当然，我们这里的模型采用的是`relu`激活函数，采用He初始化（何凯明初始化）会更加合适。感兴趣的同学可以自己动手修改并比对效果。\n",
    "\n",
    "|神经网络层数|初始化方式|early stop at epoch| val_loss | vla_acc|\n",
    "|-|-|-|-|-|\n",
    "|20|默认|\n",
    "|20|xaviier_uniform|\n",
    "|20|he_uniform|\n",
    "|...|\n",
    "\n",
    "He初始化出自论文 《Delving deep into rectifiers: Surpassing human-level performance on ImageNet classification》"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T08:07:58.663636Z",
     "start_time": "2025-01-26T08:07:58.650612Z"
    }
   },
   "source": [
    "class NeuralNetwork(nn.Module):\n",
    "    def __init__(self, layers_num=2):\n",
    "        super().__init__()\n",
    "        self.transforms = transforms # 预处理层，标准化\n",
    "        self.flatten = nn.Flatten()\n",
    "        # 输入层\n",
    "        self.linear_relu_stack = nn.Sequential(\n",
    "            nn.Linear(28 * 28, 100),\n",
    "            nn.ReLU(),\n",
    "        )\n",
    "        # 加layers_num层\n",
    "        for i in range(1, layers_num):\n",
    "            # add_module：将每一层和激活函数添加到 nn.Sequentia容器中。\n",
    "            self.linear_relu_stack.add_module(f\"Linear_{i}\", nn.Linear(100, 100))\n",
    "            self.linear_relu_stack.add_module(f\"relu\", nn.ReLU())\n",
    "        # 输出层\n",
    "        self.linear_relu_stack.add_module(\"Output Layer\", nn.Linear(100, 10))\n",
    "        \n",
    "        # 初始化权重\n",
    "        self.init_weights()\n",
    "        \n",
    "    def init_weights(self):\n",
    "        \"\"\"使用 xavier 均匀分布来初始化全连接层的权重 W\"\"\"\n",
    "        # print('''初始化权重''')\n",
    "        for m in self.modules():\n",
    "            # print(m)\n",
    "            # print('-'*50)\n",
    "            if isinstance(m, nn.Linear):#判断m是否为全连接层（线性层）\n",
    "                # https://pytorch.org/docs/stable/nn.init.html\n",
    "                nn.init.xavier_uniform_(m.weight) # xavier 均匀分布初始化权重\n",
    "                nn.init.zeros_(m.bias) # 全零初始化偏置项\n",
    "        # print('''初始化权重完成''')\n",
    "        \n",
    "    def forward(self, x):\n",
    "        # x.shape [batch size, 1, 28, 28]\n",
    "        x = self.transforms(x) #标准化\n",
    "        x = self.flatten(x)  \n",
    "        # 展平后 x.shape [batch size, 28 * 28]\n",
    "        logits = self.linear_relu_stack(x)\n",
    "        # logits.shape [batch size, 10]\n",
    "        return logits\n",
    "    \n",
    "total=0  # 统计模型参数个数\n",
    "# key为参数名，value为参数值\n",
    "for idx, (key, value) in enumerate(NeuralNetwork(20).named_parameters()):\n",
    "    print(f\"Linear_{idx // 2:>02}\\tparamerters num: {np.prod(value.shape)}\") #np.prod是计算张量的元素个数\n",
    "    total+=np.prod(value.shape)\n",
    "total"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Linear_00\tparamerters num: 78400\n",
      "Linear_00\tparamerters num: 100\n",
      "Linear_01\tparamerters num: 10000\n",
      "Linear_01\tparamerters num: 100\n",
      "Linear_02\tparamerters num: 10000\n",
      "Linear_02\tparamerters num: 100\n",
      "Linear_03\tparamerters num: 10000\n",
      "Linear_03\tparamerters num: 100\n",
      "Linear_04\tparamerters num: 10000\n",
      "Linear_04\tparamerters num: 100\n",
      "Linear_05\tparamerters num: 10000\n",
      "Linear_05\tparamerters num: 100\n",
      "Linear_06\tparamerters num: 10000\n",
      "Linear_06\tparamerters num: 100\n",
      "Linear_07\tparamerters num: 10000\n",
      "Linear_07\tparamerters num: 100\n",
      "Linear_08\tparamerters num: 10000\n",
      "Linear_08\tparamerters num: 100\n",
      "Linear_09\tparamerters num: 10000\n",
      "Linear_09\tparamerters num: 100\n",
      "Linear_10\tparamerters num: 10000\n",
      "Linear_10\tparamerters num: 100\n",
      "Linear_11\tparamerters num: 10000\n",
      "Linear_11\tparamerters num: 100\n",
      "Linear_12\tparamerters num: 10000\n",
      "Linear_12\tparamerters num: 100\n",
      "Linear_13\tparamerters num: 10000\n",
      "Linear_13\tparamerters num: 100\n",
      "Linear_14\tparamerters num: 10000\n",
      "Linear_14\tparamerters num: 100\n",
      "Linear_15\tparamerters num: 10000\n",
      "Linear_15\tparamerters num: 100\n",
      "Linear_16\tparamerters num: 10000\n",
      "Linear_16\tparamerters num: 100\n",
      "Linear_17\tparamerters num: 10000\n",
      "Linear_17\tparamerters num: 100\n",
      "Linear_18\tparamerters num: 10000\n",
      "Linear_18\tparamerters num: 100\n",
      "Linear_19\tparamerters num: 10000\n",
      "Linear_19\tparamerters num: 100\n",
      "Linear_20\tparamerters num: 1000\n",
      "Linear_20\tparamerters num: 10\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "271410"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 4
  },
  {
   "cell_type": "code",
   "source": [
    "w = torch.empty(3, 5)\n",
    "print(w)\n",
    "nn.init.eye_(w)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-01-26T08:07:58.670110Z",
     "start_time": "2025-01-26T08:07:58.664635Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[7.7247e+19, 1.6115e-42, 0.0000e+00, 0.0000e+00, 0.0000e+00],\n",
      "        [0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00],\n",
      "        [0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00]])\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "tensor([[1., 0., 0., 0., 0.],\n",
       "        [0., 1., 0., 0., 0.],\n",
       "        [0., 0., 1., 0., 0.]])"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 5
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 训练"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T08:07:58.715612Z",
     "start_time": "2025-01-26T08:07:58.670110Z"
    }
   },
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "        preds = logits.argmax(axis=-1)    # 验证集预测\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "        \n",
    "    acc = accuracy_score(label_list, pred_list)\n",
    "    return np.mean(loss_list), acc\n"
   ],
   "outputs": [],
   "execution_count": 6
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T08:08:01.755544Z",
     "start_time": "2025-01-26T08:07:58.716625Z"
    }
   },
   "source": [
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "\n",
    "class TensorBoardCallback:\n",
    "    def __init__(self, log_dir, flush_secs=10):\n",
    "        \"\"\"\n",
    "        Args:\n",
    "            log_dir (str): dir to write log.\n",
    "            flush_secs (int, optional): write to dsk each flush_secs seconds. Defaults to 10.\n",
    "        \"\"\"\n",
    "        self.writer = SummaryWriter(log_dir=log_dir, flush_secs=flush_secs)\n",
    "\n",
    "    def draw_model(self, model, input_shape):\n",
    "        self.writer.add_graph(model, input_to_model=torch.randn(input_shape))\n",
    "        \n",
    "    def add_loss_scalars(self, step, loss, val_loss):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/loss\", \n",
    "            tag_scalar_dict={\"loss\": loss, \"val_loss\": val_loss},\n",
    "            global_step=step,\n",
    "            )\n",
    "        \n",
    "    def add_acc_scalars(self, step, acc, val_acc):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/accuracy\",\n",
    "            tag_scalar_dict={\"accuracy\": acc, \"val_accuracy\": val_acc},\n",
    "            global_step=step,\n",
    "        )\n",
    "        \n",
    "    def add_lr_scalars(self, step, learning_rate):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/learning_rate\",\n",
    "            tag_scalar_dict={\"learning_rate\": learning_rate},\n",
    "            global_step=step,\n",
    "            \n",
    "        )\n",
    "    \n",
    "    def __call__(self, step, **kwargs):\n",
    "        # add loss\n",
    "        loss = kwargs.pop(\"loss\", None)\n",
    "        val_loss = kwargs.pop(\"val_loss\", None)\n",
    "        if loss is not None and val_loss is not None:\n",
    "            self.add_loss_scalars(step, loss, val_loss)\n",
    "        # add acc\n",
    "        acc = kwargs.pop(\"acc\", None)\n",
    "        val_acc = kwargs.pop(\"val_acc\", None)\n",
    "        if acc is not None and val_acc is not None:\n",
    "            self.add_acc_scalars(step, acc, val_acc)\n",
    "        # add lr\n",
    "        learning_rate = kwargs.pop(\"lr\", None)\n",
    "        if learning_rate is not None:\n",
    "            self.add_lr_scalars(step, learning_rate)\n"
   ],
   "outputs": [],
   "execution_count": 7
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T08:08:01.760756Z",
     "start_time": "2025-01-26T08:08:01.755544Z"
    }
   },
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=5000, save_best_only=True):\n",
    "        \"\"\"\n",
    "        Save checkpoints each save_epoch epoch. \n",
    "        We save checkpoint by epoch in this implementation.\n",
    "        Usually, training scripts with pytorch evaluating model and save checkpoint by step.\n",
    "\n",
    "        Args:\n",
    "            save_dir (str): dir to save checkpoint\n",
    "            save_epoch (int, optional): the frequency to save checkpoint. Defaults to 1.\n",
    "            save_best_only (bool, optional): If True, only save the best model or save each model at every epoch.\n",
    "        \"\"\"\n",
    "        self.save_dir = save_dir\n",
    "        self.save_step = save_step\n",
    "        self.save_best_only = save_best_only\n",
    "        self.best_metrics = -1\n",
    "        \n",
    "        # mkdir\n",
    "        if not os.path.exists(self.save_dir):\n",
    "            os.mkdir(self.save_dir)\n",
    "        \n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return\n",
    "        \n",
    "        if self.save_best_only:\n",
    "            assert metric is not None\n",
    "            if metric >= self.best_metrics:\n",
    "                # save checkpoints\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"best.ckpt\"))\n",
    "                # update best metrics\n",
    "                self.best_metrics = metric\n",
    "        else:\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "\n"
   ],
   "outputs": [],
   "execution_count": 8
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T08:08:01.767395Z",
     "start_time": "2025-01-26T08:08:01.761256Z"
    }
   },
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ],
   "outputs": [],
   "execution_count": 9
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T08:08:01.776577Z",
     "start_time": "2025-01-26T08:08:01.767395Z"
    }
   },
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    "                preds = logits.argmax(axis=-1)\n",
    "            \n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())    \n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"acc\": acc, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss, val_acc = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"acc\": val_acc, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "                    \n",
    "                    # 1. 使用 tensorboard 可视化\n",
    "                    if tensorboard_callback is not None:\n",
    "                        tensorboard_callback(\n",
    "                            global_step, \n",
    "                            loss=loss, val_loss=val_loss,\n",
    "                            acc=acc, val_acc=val_acc,\n",
    "                            lr=optimizer.param_groups[0][\"lr\"],\n",
    "                            )\n",
    "                    \n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), metric=val_acc)\n",
    "\n",
    "                    # 3. 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(val_acc)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # update step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 100\n",
    "\n",
    "model = NeuralNetwork(layers_num=10)\n"
   ],
   "outputs": [],
   "execution_count": 10
  },
  {
   "cell_type": "code",
   "source": [
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)\n",
    "\n",
    "# 1. tensorboard 可视化\n",
    "tensorboard_callback = TensorBoardCallback(\"runs\")\n",
    "tensorboard_callback.draw_model(model, [1, 28, 28])\n",
    "# 2. save best\n",
    "save_ckpt_callback = SaveCheckpointsCallback(\"checkpoints\", save_best_only=True)\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=10, min_delta=0.001)\n",
    "\n",
    "model = model.to(device)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-01-26T08:08:01.920814Z",
     "start_time": "2025-01-26T08:08:01.777079Z"
    }
   },
   "outputs": [],
   "execution_count": 11
  },
  {
   "cell_type": "code",
   "source": [
    "record = training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    val_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    "    )"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-01-26T08:15:33.941801Z",
     "start_time": "2025-01-26T08:08:01.921803Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/375000 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "3ceb1592b7c240b9b4b57aa180d5b31c"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 29 / global_step 108750\n"
     ]
    }
   ],
   "execution_count": 12
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T08:15:34.191049Z",
     "start_time": "2025-01-26T08:15:33.942805Z"
    }
   },
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    fig_num = len(train_df.columns)\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(6 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):    \n",
    "        axs[idx].plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        axs[idx].grid()\n",
    "        axs[idx].legend()\n",
    "        axs[idx].set_xlabel(\"step\")    \n",
    "    plt.show()\n",
    "\n",
    "plot_learning_curves(record, sample_step=10000)  #横坐标是 steps"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 1200x500 with 2 Axes>"
      ],
      "image/png": "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Pm2zMWHCBGvLli1tbTakxYS0VpHKr6E2NlJNV9rLyCaHiZdfflld37Nnj9+Ngzv7QqHF3/7ueorHevfxWK/hsd41Huur8FjvXSETVAvpLSh/XKWXoLQ/kJ5qgUz+AzjbpJ+lTn7ZbrvtNlXOXn6JL7jgAnUwtrd7927r2LFjVd84+cNy3333Wc1ms8Njfv31V2ufPn3U2J100kkO3yMQxrf6gTaUx+V///uf+hAhHwC6dOlifffddx2+Lq0LHnvsMfVHUB5zxhlnWLds2eLwmCNHjqg/mtLbUVqPXHvtteqPsz3pIygtPeQ15AAmfxCr+/LLL62dOnVSYyMtS3766Serr+Tn56vfEXmvoqOj1fsp/Rvt20CEytjI77Wzvy3yYcTfxsGdfaHQ429/dz3BY737eKyvwmO9czzWV+Gx3rvC5B9fz5YTERERERERBaKQWFNNRERERERE1BAYVBMRERERERHVE4NqIiIiIiIionpiUE1ERERERERUTwyqiYiIiIiIiOqJQTURERERERFRPTGoJiIiIiIiIqonBtVERERERERE9cSgmigIXXPNNTj//PN9vRtERETUQHisJ/IfDKqJiIiIiIiI6olBNVEA+/rrr9GzZ0/ExMSgadOmGDlyJB544AF89NFH+P777xEWFqa2hQsXqsfv27cPl156KZKSkpCSkoLzzjsPu3fvPuGs96RJk9CsWTMkJCTglltugclk8uFPSUREFLp4rCfyfxG+3gEiqp+srCxcdtlleP7553HBBRegoKAAv//+O66++mrs3bsX+fn5+PDDD9Vj5aBqNpsxZswYDB48WD0uIiIC//d//4czzzwTa9euRWRkpHrs/PnzER0drQ7OchC+9tpr1UH8mWee8fFPTEREFFp4rCcKDAyqiQL4QFteXo4LL7wQbdu2VffJmWwhZ7PLysqQnp5ue/ynn34Ki8WC999/X53RFnIgljPZclAdPXq0uk8OuNOmTUNsbCy6d++Op556Sp0Rf/rppxEezuQWIiKixsJjPVFg4P8aogDVu3dvnHHGGergeskll+C9997DsWPHXD5+zZo12L59O+Lj4xEXF6c2OatdWlqKHTt2OLyuHGR1cra7sLBQpZMRERFR4+GxnigwcKaaKEAZDAbMmzcPixcvxty5c/HGG2/gX//6F/766y+nj5eDZb9+/fDZZ5+d8DVZU0VERET+hcd6osDAoJoogElq19ChQ9X2+OOPq9SwmTNnqrSuiooKh8f27dsXM2bMQFpamipKUtNZ7pKSEpVWJpYuXarOdLdu3brBfx4iIiJyxGM9kf9j+jdRgJKz1M8++yyWL1+uipV8++23yMnJQdeuXZGZmakKkmzZsgW5ubmqcMkVV1yB1NRUVQVUipfs2rVLra+68847sX//ftvrSvXP66+/Hhs3bsSsWbPwxBNPYOLEiVxjRURE1Mh4rCcKDJypJgpQcgZ60aJFePXVV1X1Tzlz/dJLL2Hs2LHo37+/OojKpaSC/frrrxg+fLh6/EMPPaQKnkgF0ZYtW6q1WvZns+V2x44dMWzYMFUARaqOPvnkkz79WYmIiEIRj/VEgSHMarVafb0TROQfpHfl8ePH8d133/l6V4iIiKgB8FhP5H3M8SAiIiIiIiKqJwbVRERERERERPXE9G8iIiIiIiKieuJMNREREREREVE9MagmIiIiIiIiqicG1URERERERET1xKCaiIiIiIiIqJ4YVBMRERERERHVE4NqIiIiIiIionpiUE1ERERERERUTwyqiYiIiIiIiOqJQTURERERERER6uf/AaOq3DfullF5AAAAAElFTkSuQmCC"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 13
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T08:15:35.281356Z",
     "start_time": "2025-01-26T08:15:34.192623Z"
    }
   },
   "source": [
    "# dataload for evaluating\n",
    "\n",
    "# load checkpoints\n",
    "model.load_state_dict(torch.load(\"checkpoints/best.ckpt\", weights_only=False,map_location=\"cpu\"))\n",
    "\n",
    "model.eval()\n",
    "loss, acc = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\\naccuracy: {acc:.4f}\")"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.3919\n",
      "accuracy: 0.8817\n"
     ]
    }
   ],
   "execution_count": 14
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-26T08:15:35.284208Z",
     "start_time": "2025-01-26T08:15:35.282352Z"
    }
   },
   "cell_type": "code",
   "source": "",
   "outputs": [],
   "execution_count": 14
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "pytorch",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.8"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
